//////////////////////////////////////////////////////////////////////////
#include "Config/Config.hpp"
#include "Config/JSONHelpers.hpp"
#include <fstream>


//////////////////////////////////////////////////////////////////////////
using namespace Config;


//////////////////////////////////////////////////////////////////////////
class DefaultFileInterface : public FileInterface
{
public:
	void				Write( const std::string& name, const std::string& data )
	{
		std::ofstream	outStream( name );
		if( outStream.bad() )
			return;

		outStream << string_cast< std::string >( data );
	}


	const std::string	Read( const std::string& name )
	{
		std::ifstream	inStream( name );

		if( inStream.bad() )
			return std::string();

		std::string	data(	(std::istreambuf_iterator< char >( inStream )),
			std::istreambuf_iterator< char >()				);

		return data;
	}

}	sFileInterface;


//////////////////////////////////////////////////////////////////////////
Config::Initializer*	Config::Instance	=	nullptr;


//////////////////////////////////////////////////////////////////////////
Initializer::Initializer( const std::string& filename, FileInterface* fi )
:	mFilename( filename )
,	mpFileInterface( fi ? fi : &sFileInterface )
{
	assert( Instance==nullptr );
	Instance	=	this;
	Load();
}


Initializer::~Initializer()
{
	assert( Instance!=nullptr );
	Instance	=	nullptr;
	Save();
}


//////////////////////////////////////////////////////////////////////////
void Initializer::Load()
{
	std::string	tweakData( mpFileInterface->Read( mFilename ) );

	JSONValue*	root		=	JSON::Parse( tweakData.c_str() );

	if( root )
	{
		JSONValue*	registry	=	root->Child( L"Registry" );

		if( registry && registry->IsArray() )
		{
			_ReadRegistry( registry->AsArray() );
		}
		delete root;
	}
}


void Initializer::Save()
{
	// Update global/static items.
	_UpdateInitializers();

	// Build the registration array.
	JSONArray	registry;
	for( auto& it : mRegistry )
	{
		JSONObject	entry;
		entry[ L"Name" ]	=	new JSONValue( string_cast< std::wstring >( it.first ) );

		std::wstring	ss		=	string_cast< std::wstring >( it.second );
		const wchar_t*	strVal	=	ss.c_str();
		JSONValue*		value	=	JSONValue::Parse( &strVal );

		entry[ L"Value" ]	=	value;

		registry.push_back( new JSONValue( entry ) );
	}

	// Add to an object.
	JSONObject		outObject;
	outObject[ L"Registry" ]	=	new JSONValue( registry );

	// Turn into a string for writing.
	JSONValue		itemSave( outObject );
	std::wstring	saveString	=	itemSave.Stringify() + L"\n";
	_ReformatOutput( saveString );

	//////////////////////////////////////////////////////////////////////////
	mpFileInterface->Write( mFilename, string_cast< std::string >( saveString ) );
}


//////////////////////////////////////////////////////////////////////////
void Initializer::Initialize( Config::Item& i )
{
	auto	it	=	mRegistry.find( i.Key() );

	if( it==mRegistry.end() )
		mRegistry[ i.Key() ]	=	_ItemToString( i );
	else
	{
		std::wstring	vStr	=	string_cast< std::wstring >( it->second );
		const wchar_t*	vStrp	=	vStr.c_str();
		JSONValue*		value	=	JSONValue::Parse( &vStrp );
		i.Load( *value );
		delete value;
	}
}


void Initializer::Store( Config::Item& i )
{
	auto	it	=	mRegistry.find( i.Key() );
	assert( it!=mRegistry.end() );

	// Update the item in registry.  Don't actually remove it.
	mRegistry[ i.Key() ]	=	_ItemToString( i );
}


//////////////////////////////////////////////////////////////////////////
void Initializer::Update()
{
	for( const auto& it : mUpdatables )
		it.second();
}


void Initializer::AddUpdater( void* i, Update_t u )
{
	mUpdatables.push_back( UpdatePair_t( i, u ) );
}


void Initializer::RemoveUpdater( void* i )
{
	for( auto it = mUpdatables.begin();
		it!=mUpdatables.end();
		++it )
	{
		if( it->first == i )
		{
			mUpdatables.erase( it );
			return;
		}
	}
}


//////////////////////////////////////////////////////////////////////////
void Initializer::_ReadRegistry( const JSONArray& array )
{
	for( const auto& it : array )
	{
		if( it->IsObject() )
		{
			const JSONObject&	object	=	it->AsObject();

			if( object.at( L"Name" )->IsString() )
			{
				std::string			name	=	string_cast< std::string >( object.at( L"Name" )->AsString() );
				const JSONValue*	jv		=	object.at( L"Value" );
				mRegistry[ name ]	=	string_cast< std::string >( jv->Stringify() );

				Item*	item		=	InitializerList::Find( name );
				if( item )
					item->Load( *jv );
			}
		}
	}
}


std::string Initializer::_ItemToString( Item& it ) const
{
	JSONValue*	jsonVal		=	it.Save();
	std::string	valString	=	string_cast< std::string >( jsonVal->Stringify() );
	delete jsonVal;
	return valString;
}


void Initializer::_UpdateInitializers()
{
	InitializerList*	current	=	InitializerList::First();
	while( current )
	{
		mRegistry[ current->Item()->Key() ]	=	_ItemToString( *(current->Item()) );
		current	=	current->Next();
	}
}


void Initializer::_ReformatOutput( std::wstring& str ) const
{
	bool	onNewLine	=	true;
	size_t	indent		=	0;
	size_t	nextDelim	=	0;
	while( nextDelim!=std::wstring::npos )
	{
		const wchar_t		tab			=	L'\t';
		const wchar_t		delims[]	=	L",[{}]:";

		size_t	old		=	nextDelim;
		nextDelim		=	str.find_first_of( delims, nextDelim );

		if( nextDelim > old )
		{
			str.insert( old, std::wstring( onNewLine ? indent : 1, tab ) );
			nextDelim	+=	onNewLine ? indent : 1;
			onNewLine	=	false;
		}

		if( nextDelim!=std::wstring::npos )
		{
			const wchar_t	repChar	=	str.at( nextDelim );
			switch( repChar )
			{
			case L',':
				{
					str			=	str.replace( nextDelim, 1, std::wstring( 1, repChar ) + L'\n' );
					nextDelim	+=	2;
					onNewLine	=	true;
				}
				break;
			case L'[':
			case L'{':
				{
					if( !onNewLine )
					{
						str			=	str.replace( nextDelim, 1, L'\n' + std::wstring( indent, tab ) + std::wstring( 1, repChar ) + L'\n' );
						nextDelim	+=	1+indent+1+1;
					}
					else
					{
						str			=	str.replace( nextDelim, 1, std::wstring( indent, tab ) + std::wstring( 1, repChar ) + L'\n' );
						nextDelim	+=	indent + 2;
					}
					onNewLine	=	true;
					++indent;
				}
				break;
			case L']':
			case L'}':
				{
					--indent;
					str			=	str.replace( nextDelim, 1, L'\n' + std::wstring( indent, tab ) + std::wstring( 1, repChar ) );
					nextDelim	+=	1 + indent + 1;
					onNewLine	=	true;
				}
				break;
			case L':':
				{
					str			=	str.replace( nextDelim, 1, std::wstring( 1, tab ) + L':' );
					nextDelim	+=	2;
					onNewLine	=	false;
				}
				break;
			}
		}
	}
}
